/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.agera.content;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import ohos.app.Context;
import ohos.data.preferences.Preferences;
import ohos.event.commonevent.CommonEventData;
import ohos.event.commonevent.CommonEventManager;
import ohos.event.commonevent.CommonEventSubscribeInfo;
import ohos.event.commonevent.CommonEventSubscriber;
import ohos.event.commonevent.MatchingSkills;
import ohos.rpc.RemoteException;

import com.google.agera.ActivationHandler;
import com.google.agera.BaseObservable;
import com.google.agera.NonNull;
import com.google.agera.Updatable;
import com.google.agera.UpdateDispatcher;
import com.google.agera.Observable;
import static com.google.agera.Preconditions.checkNotNull;





/**
 * Utility methods for obtaining {@link Observable} instances.
 *
 * <p>Any {@link Observable} created by this class have to be created from a {@link ohos.eventhandler.EventRunner} thread
 * or they will throw an {@link IllegalStateException}
 */
public final class ContentObservables {
    private static Logger logger = Logger.getLogger("ContentObservables");

    /**
     * Returns an {@link Observable} that notifies added {@link Updatable}s that the input
     * {@code actions} have been received through the broadcast mechanism.
     *
     * <p>Since {@link ActivationHandler#observableDeactivated(UpdateDispatcher)} is called
     * asynchronously, using an activity context here will cause @link StrictMode to
     * report leaked registration objects. This can be avoided using the application context instead.
     *
     * @param actions String...
     * @return Observable
     */
    @NonNull
    public static Observable broadcastObservable(@NonNull final String... actions) {
        return new BroadcastObservable(actions);
    }

    /**
     * Returns an {@link Observable} that notifies added {@link Updatable}s that
     * {@code keys} in {@link Preferences} has been changed.
     *
     * @param keys String...
     * @param preferences Preferences
     * @return Observable
     */
    @NonNull
    public static Observable sharedPreferencesObservable(
            @NonNull final Preferences preferences, @NonNull final String... keys) {
        return new PreferencesObservable(preferences, keys);
    }

    private static final class BroadcastObservable extends BaseObservable {
        @NonNull
        private final CommonEventSubscriber broadcastReceiver;

        private final List<String> actionList;

        BroadcastObservable(@NonNull final String... actions) {
            this(null, actions);
        }

        BroadcastObservable(final Context applicationContext, @NonNull final String... actions) {
            MatchingSkills filter = new MatchingSkills();
            this.actionList = new ArrayList<>();
            for (final String action : actions) {
                filter.addEvent(action);
                this.actionList.add(action);
            }
            CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(filter);
            this.broadcastReceiver =
                    new CommonEventSubscriber(subscribeInfo) {
                        @Override
                        public void onReceiveEvent(CommonEventData commonEventData) {
                            if (actionList.contains(commonEventData.getIntent().getAction())) {
                                dispatchUpdate();
                            }
                        }
                    };
        }

        @Override
        public void observableActivated() {
            try {
                CommonEventManager.subscribeCommonEvent(broadcastReceiver);
            } catch (RemoteException e) {
                logger.log(Level.INFO, e.getMessage());
            }
        }

        @Override
        public void observableDeactivated() {
            try {
                CommonEventManager.unsubscribeCommonEvent(broadcastReceiver);
            } catch (RemoteException e) {
                logger.log(Level.INFO, e.getMessage());
            }
        }
    }

    private static final class PreferencesObservable extends BaseObservable implements Preferences.PreferencesObserver {
        @NonNull
        private final Preferences preferences;

        @NonNull
        private final Set<String> keys;

        PreferencesObservable(@NonNull final Preferences preferences, @NonNull final String... keys) {
            this.keys = new HashSet<>(Arrays.asList(keys));
            this.preferences = checkNotNull(preferences);
        }

        @Override
        public void observableActivated() {
            preferences.registerObserver(this);
        }

        @Override
        public void observableDeactivated() {
            preferences.unregisterObserver(this);
        }

        @Override
        public void onChange(Preferences preferences, String key) {
            if (keys.contains(key)) {
                dispatchUpdate();
            }
        }
    }

    private ContentObservables() {}
}
